﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using QUT.Bio.SilverMap.ObjectModel;
using QUT.Bio.Util;

namespace BlastExplorer.SilverMapDataProducer {

	/// <summary>
	/// Implement the ILinearDomain interface to be used in conjunction with IDataProvider to map the 
	/// domain into SilverMap.
	/// </summary>

	public class Sequence: ILinearDomain {
		private SequenceKey key;
		private string name;
		private readonly List<QUT.Bio.Util.Xref> xref = new List<QUT.Bio.Util.Xref>();
		private string label;
		private long length;
		private string organism;
		private string sequenceData;
		private readonly HashList<SequenceKey> embeddedQuerySequences = new HashList<SequenceKey>();

		#region Constructor( key, organism, name, label, length )
		/// <summary>
		/// Initialise a sequence, recording its key and basic metadata.
		/// </summary>
		/// <param name="key">The combined (source, id) pair that uniquely identifies the sequence</param>
		/// <param name="organism">The genome from which the sequence is sourced.</param>
		/// <param name="name">The sequence name.</param>
		/// <param name="label">A short label suitable for display in the map.</param>
		/// <param name="length">The sequence length.</param>
		/// <param name="containsQuerySequence">An indicator that should be true iff the sequence contains the original seed query sequence. That is, an exact match to the query from the database.</param>

		public Sequence (
			SequenceKey key,
			string organism,
			string name,
			string label,
			long length
		) {
			this.key = key;
			this.organism = organism;
			this.name = name;
			this.label = label;
			this.length = length;
		}
		#endregion

		#region Constructor( source, id, organism, name, label, length )
		/// <summary>
		/// Initialise a sequence, recording its key and basic metadata.
		/// </summary>
		/// <param name="source">The source from qhich this sequence is obtained.</param>
		/// <param name="id">The source-specific identifier of the sequence.</param>
		/// <param name="organism">The genome from which the sequence is sourced.</param>
		/// <param name="name">The sequence name.</param>
		/// <param name="label">A short label suitable for display in the map.</param>
		/// <param name="length">The sequence length.</param>

		public Sequence (
			SequenceSource source,
			string id,
			string organism,
			string name,
			string label,
			long length
		)
			: this( new SequenceKey( source, id ), organism, name, label, length ) {
		}
		#endregion

		#region Property: Name
		/// <summary>
		/// Gets the the full "organsim - name" of the sequence.
		/// </summary>

		public string Name {
			get {
				return organism + " - " + name;
			}
		}
		#endregion

		#region Property: Label
		/// <summary>
		/// Get the label of the sequence.
		/// </summary>

		public string Label {
			get {
				return label;
			}
		}
		#endregion

		#region Property: Xref
		/// <summary>
		/// Get the list of cross references associated with the sequence.
		/// </summary>

		public IEnumerable<QUT.Bio.Util.Xref> Xref {
			get {
				return xref;
			}
		}
		#endregion

		#region Property: SequenceData
		/// <summary>
		/// Get or set the sequence data. This is not available in general, until we do a query of 
		/// some sort.
		/// </summary>

		public string SequenceData {
			get {
				return sequenceData;
			}
			set {
				sequenceData = value;
			}
		}
		#endregion

		#region Property: Length
		/// <summary>
		/// Gets the length of the sequence.
		/// </summary>

		[Displayed]
		public long Length {
			get {
				return length;
			}
		}
		#endregion

		#region Method: CompareTo( ILinearDomain )
		/// <summary>
		/// Compare the sequence to another by comparing keys.
		/// </summary>
		/// <param name="other">A sequence for comparison.</param>
		/// <returns>Less than 0, 0 or greater than 0, according to the comparative ordering of the sequence relative to the other.</returns>

		public int CompareTo ( ILinearDomain other ) {
			return key.CompareTo( ( (Sequence) other ).key );
		}
		#endregion

		#region Method: CompareTo( Object )
		/// <summary>
		/// Compare the sequence to another.
		/// </summary>
		/// <param name="other">A sequence for comparison.</param>
		/// <returns>Less than 0, 0 or greater than 0, according to the comparative ordering of the sequence relative to the other.</returns>

		public int CompareTo ( object x ) {
			return CompareTo( (ILinearDomain) x );
		}
		#endregion

		#region Method: CompareTo( Object )
		/// <summary>
		/// Determine equality of the sequqnce to another. Equality is the same as key equality.
		/// </summary>
		/// <param name="other">A sequence for comparison.</param>
		/// <returns>True iff the sequqnces are the same.</returns>

		public bool Equals ( ILinearDomain other ) {
			return CompareTo( other ) == 0;
		}
		#endregion

		#region Method: Equals( object )
		/// <summary>
		/// Determine equality of the sequqnce to another. Equality is the same as key equality.
		/// </summary>
		/// <param name="obj">A sequence for comparison.</param>
		/// <returns>True iff the sequqnces are the same.</returns>

		public override bool Equals ( object obj ) {
			return Equals( (Sequence) obj );
		}
		#endregion

		#region Method: GetHashCode
		/// <summary>
		/// Get a hashcode that is consistent with Equals.
		/// </summary>
		/// <returns>A hash code for the sequence key.</returns>

		public override int GetHashCode () {
			return key.GetHashCode();
		}
		#endregion

		#region Property: Source
		/// <summary>
		/// Get the source of this sequence.
		/// </summary>

		[Displayed]
		public SequenceSource Source {
			get {
				return key.Source;
			}
		}
		#endregion

		#region Property: Id
		/// <summary>
		/// Get the Id of the sequence.
		/// </summary>

		[Displayed]
		public string Id {
			get {
				return key.Id;
			}
		}
		#endregion

		#region Method: ToXml
		/// <summary>
		/// Serialize as Xml.
		/// </summary>
		/// <returns>An Xml element that represents the sequence.</returns>

		public XElement ToXml () {
			XElement xrefs = new XElement( "Xrefs" );

			foreach ( var xref in Xref ) {
				Xref xref_ = (Xref) xref;
				xrefs.Add( xref_.ToXml() );
			}

			XElement queries = new XElement( "Queries" );

			foreach ( SequenceKey queryKey in embeddedQuerySequences ) {
				queries.Add( queryKey.ToXml() );
			}

			return new XElement( "Sequence",
				new XElement( "Source", key.Source ),
				new XElement( "Id", key.Id ),
				new XElement( "Organism", organism ),
				new XElement( "Name", name ),
				new XElement( "Label", label ),
				new XElement( "Length", length ),
				new XElement( "SequenceData", sequenceData == null ? "null" : sequenceData ),
				queries,
				xrefs
			);
		}
		#endregion

		#region Method: FromXml
		/// <summary>
		/// Deserialise a sequence from an xml element.
		/// </summary>
		/// <param name="element">An XElement that contains a serialized Sequence object.</param>
		/// <returns></returns>

		public static Sequence FromXml ( XElement element ) {
			Sequence seq = new Sequence(
						   element.Element( "Source" ) == null
						   ? SequenceSource.Genbank
						   : (SequenceSource) Enum.Parse( typeof( SequenceSource ), element.Element( "Source" ).Value ),
						   element.Element( "Id" ).Value,
						   element.Element( "Organism" ).Value,
						   element.Element( "Name" ).Value,
						   element.Element( "Label" ).Value,
						   long.Parse( element.Element( "Length" ).Value )
			);

			XElement child = element.Element( "SequenceData" );

			if ( child != null && child.Value != "null" ) {
				seq.sequenceData = child.Value;
			}

			List<QUT.Bio.Util.Xref> sequenceXrefs = (List<QUT.Bio.Util.Xref>) seq.Xref;

			foreach ( XElement xrefElement in element.Element( "Xrefs" ).Nodes() ) {
				Xref xref = BlastExplorer.SilverMapDataProducer.Xref.FromXml( xrefElement );

				if ( !sequenceXrefs.Contains( xref ) ) {
					sequenceXrefs.Add( xref );
				}
			}

			child = element.Element( "Queries" );

			if ( child != null ) {
				foreach ( XElement queryElement in child.Nodes() ) {
					seq.embeddedQuerySequences.Add( new SequenceKey( queryElement ) );
				}
			}

			return seq;
		}
		#endregion

		#region Property: Key
		/// <summary>
		/// Gets the identity key for this sequence.
		/// </summary>
		public SequenceKey Key {
			get {
				return key;
			}
		}
		#endregion

		#region Property: ContainsQuerySequence
		/// <summary>
		/// Gets the flag "contains query sequence". This is a first brush hacky attempt to deal with the tricky issue associated with multiple genes encoding the same identical amino acid sequence.
		/// </summary>

		public bool ContainsQuerySequence {
			get {
				return embeddedQuerySequences.Count > 0;
			}
		}
		#endregion

		#region Property: EmbeddedQuerySequences
		/// <summary>
		/// Gets a list of query sequences that encode the same sequence as the supplied value.
		/// <para>This is part of my preliminary attempt to deal with the situation where
		/// multiple genes are represented by a single sequence.
		/// </para>
		/// </summary>

		public HashList<SequenceKey> EmbeddedQuerySequences {
			get {
				return embeddedQuerySequences;
			}
		}
		#endregion
	}
}
